# coding: utf-8

__doc__ = """ LDAP工具模块,所使用的所有python-ldap api全部为同步方法.（非异步）"""

import ldap
from ldap import modlist
import ldif
from StringIO import StringIO

from jd_oss.tools import logger
# from jd_oss.settings import (
        #     LDAP_BASE_DN,LDAP_ROOT_DN,LDAP_ROOT_PW,LDAP_HOST_URL
        # )

LDAP_HOST_URL = "ldap://10.10.41.110:389"
LDAP_BASE_DN = "dc=jidongnet, dc=com"
LDAP_ROOT_DN = "cn=admin,dc=jidongnet,dc=com"
LDAP_ROOT_PW = "jidongnet"


class MyLdap():
    """
    ldap管理类
    """
    def __init__(self,
                 host_url,
                 base_dn,
                 root_dn,
                 root_pw):
        self.ldap_host = host_url
        self.ldap_base_dn = base_dn
        self.ldap_root_dn = root_dn
        self.ldap_root_pw = root_pw
        self.conn = ldap.initialize(host_url)
        self.conn.set_option(ldap.OPT_REFERRALS, 0)
        self.conn.protocol_version = ldap.VERSION3
        self.conn.simple_bind_s(root_dn, root_pw)

    def __del__(self):
        self.conn.unbind_s()

    @staticmethod
    def get_ldap():
        return MyLdap(LDAP_HOST_URL, LDAP_BASE_DN,
                        LDAP_ROOT_DN, LDAP_ROOT_PW)

    def search(self, filterstr='(objectClass=*)', dn='',scope=ldap.SCOPE_SUBTREE, attr=None):
        """搜索
           Args:
              filterstr(str): 匹配条件.支持ldap原生条件匹配.默认为"(objectClass=*)"匹配所有.
              scope:  搜索层级,
                  ldap.SCOPE_BASE 只搜索当前级
                  ldap.SCOPE_ONELEVEL 搜索直接关联的子级,即只往下搜索一级.
                  ldap.SCOPE_SUBTREE 遍历搜索所有的子级.
              dn(str)   : 搜索的起始位置.
              attr(list)： 指定返回的属性字段.
           Returns:
              list: 包含LDAPSearchResult对象的列表
        """
        dn = dn if dn else self.ldap_base_dn

        try:
            ldap_result = self.conn.search_s(
                dn, scope, filterstr, attr)
            return LDAPSearchResult.get_search_results(ldap_result)
        except ldap.LDAPError, e:
            print e


    def add(self, dn, attrs):
        """ 添加操作
            不捕获 LDAPError 异常.由调用者处理 """
        ldif = modlist.addModlist(attrs)
        self.conn.add_s(dn, ldif)

    def modify(self, dn, ops,*op_type):
        """ 修改操作
            不捕获 LDAPError 异常.由调用者处理
            Args:
                dn: 条目具体位置
                ops: 操作任务列表.
                    ( ldap.MOD_ADD, 'givenName', 'Francis' ) or
                    ( 0, 'givenName', 'Francis' )
                op_type(int): 无实际意义参数,只是为了说明数字对应的操作类型.
                    op_type:ldap.MOD_ADD : 0
                    ldap.MOD_DELETE : 1
                    ldap.MOD_REPLACE : 2

        """
        try:
            self.conn.modify_s(dn, ops)
        except TypeError as e:
            logger.warn(u"修改异常,尝试 utf-8 编码.")
            # 处理下编码重试
            ops = map(lambda x:
                    tuple(map(
                        lambda y: y if type(y) == int else y.encode('utf-8'),
                        x)
                    ),
                ops)
            try:
                self.conn.modify_s(dn, ops)
            except Exception:
                pass
            else:
                logger.warn(u"utf-8编码,修改成功.")


    def delete(self, dn):
        """ 删除单个条目.
            不捕获 LDAPError 异常.由调用者处理
        """
        self.conn.delete_s(dn)

class LDAPSearchResult:
    """A class to model LDAP results.
    """

    dn = ''

    @staticmethod
    def get_search_results(results):
        """Given a set of results, return a list of LDAPSearchResult
        objects.
        """
        res = []

        if type(results) == tuple and len(results) == 2 :
            (code, arr) = results
        elif type(results) == list:
            arr = results

        if len(results) == 0:
            return res

        for item in arr:
            res.append( LDAPSearchResult(item) )

        return res

    def __init__(self, entry_tuple):
        """Create a new LDAPSearchResult object."""
        (dn, attrs) = entry_tuple
        if dn:
            self.dn = dn
        else:
            return

        self.attrs = dict(attrs)

    def __getitem__(self,key):
        value = self.attrs[key]
        return ''.join(value) if len(value) == 1 else value


    def __setitem__(self,key,value):
        self.attrs[key] = value

    def __len__(self):
        return len(self.attrs)

    def __str__(self):
        return self.pretty_print()

    def get_attributes(self):
        """Get a dictionary of all attributes.
        get_attributes()->{'name1':['value1','value2',...],
				'name2: [value1...]}
        """
        return self.attrs

    def set_attributes(self, attr_dict):
        """Set the list of attributes for this record.

        The format of the dictionary should be string key, list of
        string alues. e.g. {'cn': ['M Butcher','Matt Butcher']}

        set_attributes(attr_dictionary)
        """

        self.attrs = dict(attr_dict)

    def has_attribute(self, attr_name):
        """Returns true if there is an attribute by this name in the
        record.

        has_attribute(string attr_name)->boolean
        """
        return self.attrs.has_key( attr_name )

    def get_attr_values(self, key):
        """Get a list of attribute values.
        get_attr_values(string key)->['value1','value2']
        """
        return self.attrs[key]

    def get_attr_names(self):
        """Get a list of attribute names.
        get_attr_names()->['name1','name2',...]
        """
        return self.attrs.keys()

    def get_dn(self):
        """Get the DN string for the record.
        get_dn()->string dn
        """
        return self.dn


    def pretty_print(self):
        """Create a nice string representation of this object.

        pretty_print()->string
        """
        s = "DN: " + self.dn + "\n"
        for a, v_list in self.attrs.iteritems():
            s = s + "Name: " + a + "\n"
            for v in v_list:
                s = s + "  Value: " + v + "\n"
        return s


    def to_ldif(self):
        """Get an LDIF representation of this record.

        to_ldif()->string
        """
        out = StringIO()
        ldif_out = ldif.LDIFWriter(out)
        ldif_out.unparse(self.dn, self.attrs)
        return out.getvalue()
